package correctness;

import static org.junit.Assert.*;

import java.util.Observable;
import java.util.Observer;

import model.LogicType;
import model.editormodel.EditorModel;
import model.editormodel.Input;
import model.editormodel.Output;
import model.simulationmodel.LogicValue;
import model.simulationmodel.SimulationMessage;
import model.simulationmodel.SimulationModel;
import model.simulationmodel.SimulationMessage.SimulationMessageType;

import org.junit.Test;

/**
 * <b> Mit dieser Testklasse wird folgende Vorgabe getestet:</b> <br>
 * <br>
 * Eine Schaltung mit einem einzigen OR-Gatter, zwei Schaltern und einer Lampe
 * (und den passenden Verbindungen) wird erstellt. Einer der Schalter wird auf
 * L1 gesetzt, der andere auf L0. Die Schaltung wird simuliert, und das korrekte
 * Ergebnis an der Lampe wird ueberprueft.
 * 
 * @author Team Locsi
 * 
 */
public class SimpleSimulationTest {

	static SimulationModel sModel;
	boolean simulationRunning;

	/**
	 * Testet die Simulation eines OR-Gatters mit einem Schalter und einer
	 * Lampe.
	 */
	@Test
	public void testStartSimulation() {

		// Initialisieren einer neuen Schaltung
		EditorModel eModel = new EditorModel();
		eModel.newCircuit();

		// Erzeugen der benoetigten logischen Elemente
		eModel.addLogicElement(LogicType.SWITCH, "SWITCH", 5, 5);
		eModel.addLogicElement(LogicType.SWITCH, "SWITCH", 5, 10);
		eModel.addLogicElement(LogicType.OR, "OR", 10, 10);
		eModel.addLogicElement(LogicType.LAMP, "LAMP", 20, 20);

		// Setzen der Verbindungen zwischen den Schaltern und dem OR-Gatter
		eModel.setConnection(
				eModel.getCircuitModel().getUnmodifableElementsOnEditor()
						.get("SWITCH_1").getOutputs().get(Output.STD_O),
				eModel.getCircuitModel().getUnmodifableElementsOnEditor()
						.get("OR_3").getInputs().get(Input.STD_A));
		eModel.setConnection(
				eModel.getCircuitModel().getUnmodifableElementsOnEditor()
						.get("SWITCH_2").getOutputs().get(Output.STD_O),
				eModel.getCircuitModel().getUnmodifableElementsOnEditor()
						.get("OR_3").getInputs().get(Input.STD_B));

		// Setzen der Verbindung zwischen OR-Gatter und Lampe
		eModel.setConnection(
				eModel.getCircuitModel().getUnmodifableElementsOnEditor()
						.get("OR_3").getOutputs().get(Output.STD_O), eModel
						.getCircuitModel().getUnmodifableElementsOnEditor()
						.get("LAMP_4").getInputs().get(Input.STD_A));

		// Erzeugen des Simulation Models
		sModel = new SimulationModel(eModel);

		// Updaten des Simulation Models
		sModel.updateSimulationElements();

		// Setzen der Werte der Schalter
		sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("SWITCH_1"))
				.setInputValue(Input.STD_A, LogicValue.L0);
		sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("SWITCH_2"))
				.setInputValue(Input.STD_A, LogicValue.L1);

		// Hinzufuegen eines PseudoObservers, damit sichergestellt werden kann,
		// dass die Simulation abgeschlossen ist, bevor auf Korrektheit getestet
		// wird
		PseudoObserver pseudoObserver = new PseudoObserver();
		sModel.addObserver(pseudoObserver);

		// Starten der Simulation
		simulationRunning = true;
		sModel.startSimulation();

		// Solange die Simulation laeuft, soll hier gewartet werden
		while (simulationRunning)
			synchronized (pseudoObserver) {
				try {
					pseudoObserver.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

		// Prueft, ob die Lampe leuchtet
		assertEquals(
				"Die Lampe leuchtet nicht",
				LogicValue.L1,
				sModel.getSimulationElements()
						.get(sModel.getLogicElements().get("LAMP_4"))
						.getLogicValue(Output.STD_O));

		// Prueft ob am Ausgang des OR Gatters L1 anliegt
		assertEquals(
				"Der Ausgang des OR-Gatters hat nicht den Wert L1",
				LogicValue.L1,
				sModel.getSimulationElements()
						.get(sModel.getLogicElements().get("OR_3"))
						.getLogicValue(Output.STD_O));
	}

	/**
	 * Innere Klasse die einen pseudoObserver zur Verfuegung stellt
	 */
	class PseudoObserver implements Observer {

		@Override
		public void update(Observable observable, Object data) {

			if (data instanceof SimulationMessage) {
				SimulationMessageType sType = ((SimulationMessage) data)
						.getMsgType();

				if (sType == SimulationMessageType.FIRST_CALC_COMPLETE) {
					synchronized (this) {
						simulationRunning = false;
						this.notifyAll();
					}
				}
			}
		}
	}
}